Séries Temporais para Projetos de Data Science
Projeto 1 - Modelagem de Séries Temporais para
Prever Usuários Ativos em um
Web Site ao Longo do Tempo - Tarefa 2
Séries Temporais para Projetos de Data Science
Projeto 1 - Modelagem de Séries Temporais para
Prever Usuários Ativos em um
Web Site ao Longo do Tempo - Tarefa 2
Roberto SSoares - LfLngLrnng
in/roberto-dos-santos-soares
Portifólio: roberto-ssoares
" [+] Faturamento,
[-] Custo,
[+] Qualidade de vida "
"Bruno Jardim"
📌 Objetivo
📚 Instalando e Carregando os Pacotes
# Para atualizar um pacote, execute o comando abaixo no terminal ou prompt de comando:
# pip install -U nome_pacote
# Para instalar a versão exata de um pacote, execute o comando abaixo no terminal ou prompt de comando:
# !pip install nome_pacote==versão_desejada
# Depois de instalar ou atualizar o pacote, reinicie o jupyter notebook.
# Instala o pacote watermark.
# Esse pacote é usado para gravar as versões de outros pacotes usados neste jupyter notebook.
# !pip install -q -U watermark
📚 Importando Bibliotecas
# Imports para manipulação de dados
import pandas as pd
# Imports para visualização de dados
import matplotlib.pyplot as plt
import matplotlib as m
import seaborn as sns
# Imports para análise e modelagem de séries temporais
import statsmodels.api as sm
from statsmodels.graphics.tsaplots import plot_acf
from statsmodels.graphics.tsaplots import plot_pacf
from statsmodels.tsa.stattools import adfuller
📚 Formatação para os gráficos e tabelas
# Formatação para os gráficos e tabelas
plt.style.use('ggplot')
pd.set_option('display.expand_frame_repr', False)
pd.set_option('display.max_columns', 500)
pd.set_option('display.width', 1000)
📚 Formatando as labels dos gráficos
# Formatando os labels dos gráficos
m.rcParams['axes.labelsize'] = 12
m.rcParams['xtick.labelsize'] = 10
m.rcParams['ytick.labelsize'] = 10
m.rcParams['text.color'] = 'k'
# Versões dos pacotes usados neste jupyter notebook
%reload_ext watermark
%watermark -a "robertossoares-lflnglrnng"
Author: robertossoares-lflnglrnng
⚙️ 1. Carregando e Explorando os Dados
# Carrega os dados
dados = pd.read_csv('../data/00-raw/dataset.csv', header = None)
📌 Observe:
⚙️ 2. Processamento e Visualização dos Dados
🚀 Ajustando o nome de cada coluna:
# Ajustando o nome de cada coluna
dados.columns = ['mes', 'usuarios_ativos']
🚀 Convertendo a coluna data de object para datetime:
# Convertendo a coluna data de object para datetime
dados['mes'] = pd.to_datetime(dados['mes'], format = '%Y-%m')
🚀 Vamos transformar a coluna de data no índice do conjunto de dados:
# Vamos transformar a coluna de data no índice do conjunto de dados.
# Isso vai facilitar nosso trabalho mais a frente.
dados_serie = dados.set_index('mes')
dados_serie.head()
| usuarios_ativos | |
|---|---|
| mes | |
| 2013-01-01 | 110 |
| 2013-02-01 | 121 |
| 2013-03-01 | 135 |
| 2013-04-01 | 127 |
| 2013-05-01 | 128 |
📌 Observe:
⚙️ 2.1. Plot da Série Temporal - Primeira Análise de Tendência
type(dados_serie)
pandas.core.frame.DataFrame
🚀 Plot da Série Temporal - aqui já observamos algumas tendências
# Plot da Série Temporal - aqui já observamos algumas tendências
dados_serie.plot(figsize = (15, 6))
plt.show()
📌 Observe:
- O gráfico acima mostra 2 componentes da série: Sazonalidade e Tendência.
- Sazonalidade: O fenômeno se repete em períodos fixos.
- Tendência: Ao longo do tempo, a série segue uma tendência de crescimento.
- Outro aspecto a considerar é o comportamento cíclico.
- Isso acontece quando o padrão de subida e descida da série não ocorre em intervalos fixos baseados em calendário.
- Deve-se tomar cuidado para não confundir efeito "cíclico" com efeito "sazonal".
- Mas, como diferenciar um padrão "cíclico" versus "sazonal"?
- Se os padrões não tiverem frequências fixas baseadas em calendário, será cíclico.
- Porque, diferentemente da sazonalidade, os efeitos cíclicos são tipicamente influenciados pelos negócios e outros fatores socioeconômicos.
⚙️ 3. Propriedades Estatísticas de Séries Temporais
📌 Observe:
📌 Observe:
Vamos explorar cada um desses conceitos:
- Distribuição Normal (ou Gaussiana)
- A distribuição normal é uma das distribuições de probabilidade mais importantes e comuns em estatística.
- É caracterizada por sua forma de sino e é simétrica em relação à sua média.
- Características:
- Simetria: A curva é simétrica ao redor da média, o que significa que a metade da população está à esquerda da média e a outra metade à direita.
- Pontos de inflexão: A curva tem pontos de inflexão onde a mudança da concavidade ocorre, localizados a um desvio padrão à esquerda e à direita da média.
- Área sob a curva: A área total sob a curva de distribuição normal é igual a 1, o que representa a totalidade da probabilidade.
- Aplicações: É utilizada para modelar fenômenos naturais, notas de testes, erros de medição, entre outros.
Média
- A média é uma medida de tendência central que descreve o valor "central" ou "típico" de um conjunto de dados.
- É calculada somando todos os valores do conjunto de dados e dividindo pelo número total de valores.
- Serve como um ponto de referência para comparar os valores individuais do conjunto de dados.
Variância
- A variância é uma medida de dispersão que indica o quão espalhados estão os valores em um conjunto de dados em relação à média.
- Quanto maior a variância, mais distantes estão os valores da média.
- Uma variância baixa indica que os dados estão agrupados perto da média, enquanto uma variância alta indica que os dados estão mais espalhados.
- A distribuição normal utiliza a média e a variância como parâmetros fundamentais para sua definição, onde a média determina o centro da distribuição e a variância determina o quão achatada ou estreita é a curva.
- Esses conceitos estão intrinsecamente ligados e são essenciais para a análise de dados e a tomada de decisões baseadas em evidências estatísticas.
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import norm
# -----------------------------
# Parâmetros da distribuição
# -----------------------------
mu, sigma = 0, 1 # média e desvio padrão
# Eixo x e densidade (PDF)
x = np.linspace(-4, 4, 1000)
y = norm.pdf(x, mu, sigma)
# -----------------------------
# Criação do gráfico
# -----------------------------
plt.figure(figsize=(12,7))
plt.plot(x, y, color='darkblue', lw=2, label='Distribuição Normal')
# Linha da média
plt.axvline(mu, color='red', linestyle='--', lw=2, label='Média (μ)')
# Pontos de inflexão (μ ± σ)
plt.axvline(mu - sigma, color='gray', linestyle=':', lw=1.5)
plt.axvline(mu + sigma, color='gray', linestyle=':', lw=1.5)
plt.text(mu - sigma - 0.4, 0.1, '-1σ\nPonto de Inflexão', color='gray', ha='center')
plt.text(mu + sigma + 0.4, 0.1, '+1σ\nPonto de Inflexão', color='gray', ha='center')
# Área sob a curva (entre -1σ e +1σ)
plt.fill_between(x, 0, y, where=(x > mu - sigma) & (x < mu + sigma), color='skyblue', alpha=0.4, label='Área ≈ 68,3%')
# -----------------------------
# Anotações de características
# -----------------------------
plt.text(mu, 0.42, 'Simetria', fontsize=12, color='red', ha='center', fontweight='bold')
plt.text(mu, 0.37, 'Metade esquerda = Metade direita', fontsize=10, color='black', ha='center')
plt.text(-3.5, 0.05, 'Caudas\n(valores extremos)', fontsize=10, color='black', ha='center')
plt.text(3.5, 0.05, 'Caudas\n(valores extremos)', fontsize=10, color='black', ha='center')
# -----------------------------
# Exemplo de aplicação
# -----------------------------
plt.text(0, -0.10,
'Exemplo de Aplicação:\n'
'A distribuição normal pode representar a variação natural de alturas humanas.\n'
'A maioria das pessoas está próxima da média (μ), enquanto poucas estão nas extremidades (±σ).',
fontsize=10, ha='center', va='top', bbox=dict(facecolor='white', alpha=0.8, edgecolor='gray'))
# -----------------------------
# Configuração final
# -----------------------------
plt.title('Distribuição Normal e Suas Características', fontsize=16, pad=15)
plt.xlabel('Valores')
plt.ylabel('Densidade de Probabilidade')
plt.legend()
plt.grid(alpha=0.3)
plt.ylim(-0.05, 0.45)
plt.show()
| Conceito | Representação visual | Explicação |
|---|---|---|
| Simetria | A curva é espelhada em relação à média (μ) | Metade dos valores está à esquerda e metade à direita |
| Pontos de Inflexão (μ ± σ) | Linhas pontilhadas cinzas | Onde a concavidade da curva muda de côncava para convexa |
| Área sob a curva | Faixa azul clara entre -1σ e +1σ | Representa ~68,3% dos valores observados |
| Exemplo prático | Estudo sobre altura de uma população | Altura de pessoas, erros de medição, notas de exames, etc. |
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import norm
# -----------------------------
# Distribuições
# -----------------------------
mu_ref, sigma_ref = 0, 1 # Processo alvo (padrão)
mu_proc, sigma_proc = 0, 2 # Processo real (mais variável)
x = np.linspace(-10, 10, 2000)
y_ref = norm.pdf(x, mu_ref, sigma_ref)
y_proc = norm.pdf(x, mu_proc, sigma_proc)
# -----------------------------
# Encontra pontos de interseção entre padrão e processo
# -----------------------------
diff = y_ref - y_proc
idx = np.where(np.diff(np.sign(diff)) != 0)[0]
# Garantindo dois pontos de interseção (simétricos nesse caso)
x1 = x[idx[0]]
x2 = x[idx[1]]
# -----------------------------
# Figura
# -----------------------------
plt.figure(figsize=(15,8))
# Curvas
plt.plot(x, y_ref, label='Padrão: μ=0, σ=1', color='tab:blue', lw=2)
plt.plot(x, y_proc, label='Produção: μ=0, σ=2', color='tab:orange', lw=2)
# Linha na média alvo
plt.axvline(mu_ref, color='gray', linestyle='--', lw=1)
plt.text(mu_ref, max(y_ref)*1.02, 'Alvo (μ)', ha='center', fontsize=10)
# -----------------------------
# ÁREA 1: Centro (entre x1 e x2)
# Aqui o processo real tem MENOS densidade que o padrão.
# Significa menos peças exatamente próximas do alvo.
# -----------------------------
plt.fill_between(
x, y_ref, y_proc,
where=(x >= x1) & (x <= x2) & (y_ref > y_proc),
color='red', alpha=0.25,
label='Área 1 - Menos peças no alvo'
)
# -----------------------------
# ÁREA 2: Cauda Esquerda (x < x1)
# Processo real tem MAIS densidade que o padrão.
# Mais peças muito abaixo da especificação.
# -----------------------------
plt.fill_between(
x, y_proc, y_ref,
where=(x < x1) & (y_proc > y_ref),
color='orange', alpha=0.35,
label='Área 2 - Excesso de peças abaixo do alvo'
)
# -----------------------------
# ÁREA 3: Cauda Direita (x > x2)
# Processo real tem MAIS densidade que o padrão.
# Mais peças muito acima da especificação.
# -----------------------------
plt.fill_between(
x, y_proc, y_ref,
where=(x > x2) & (y_proc > y_ref),
color='orange', alpha=0.35,
label='Área 3 - Excesso de peças acima do alvo'
)
# -----------------------------
# Ajustes visuais
# -----------------------------
plt.title('Controle de Qualidade: Efeito do Aumento da Variância na Produção', fontsize=15, pad=15)
plt.xlabel('Medida do Produto')
plt.ylabel('Densidade de Probabilidade')
plt.grid(alpha=0.3)
plt.legend(loc='upper right')
plt.ylim(bottom=0)
# Texto explicativo
plt.text(
0, -0.095,
(
"Interpretação:\n"
"Área 1 (vermelha): o processo real coloca MENOS peças próximas do alvo ⇒ perda de precisão.\n"
"Área 2 (laranja à esquerda): MAIS peças muito pequenas / abaixo da especificação ⇒ risco de falha, sucata ou retrabalho.\n"
"Área 3 (laranja à direita): MAIS peças muito grandes / acima da especificação ⇒ desperdício de material, problemas de encaixe ou segurança.\n"
"O aumento da variância (σ²) sem mudar a média já é suficiente para acender alertas de qualidade."
),
ha='center', va='top', fontsize=9,
bbox=dict(facecolor='white', alpha=0.9, edgecolor='gray')
)
plt.tight_layout()
plt.show()
🔍 Leitura dos três fenômenos (didático para portfólio)
Perda de precisão (Área 1)
O pico da produção real é mais baixo que o do padrão.
Menos peças saem exatamente dentro da faixa “ideal”.
Sinal de processo desajustado ou instável — mesmo que ainda “pareça” centrado.
Risco por peças abaixo da especificação (Área 2)
Mais probabilidade no lado esquerdo extremo.
Ex.: parafusos finos demais, frascos com menos volume, resistência mecânica insuficiente.
Impacto: falha de performance, devolução, perda de confiabilidade.
Risco por peças acima da especificação (Área 3)
Mais probabilidade no lado direito extremo.
Ex.: diâmetro grande demais, excesso de material, encaixe difícil, sobrecarga.
Impacto: custo maior, problemas de montagem, risco operacional.
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import norm
# -----------------------------
# Parâmetros
# -----------------------------
mu_ref, sigma_ref = 0, 1 # Processo alvo (referência)
mu_proc, sigma_proc = 0, 2 # Processo real (mais disperso)
LSL, USL = -3, 3 # Limites de Especificação
# Eixo x
x = np.linspace(-10, 10, 3000)
# Densidades
y_ref = norm.pdf(x, mu_ref, sigma_ref)
y_proc = norm.pdf(x, mu_proc, sigma_proc)
# -----------------------------
# Cálculo de Cp e Cpk para o processo real
# -----------------------------
# Cp = (USL - LSL) / (6 * sigma)
# Cpk = min( (USL - mu)/(3*sigma), (mu - LSL)/(3*sigma) )
Cp = (USL - LSL) / (6 * sigma_proc)
Cpk = min((USL - mu_proc) / (3 * sigma_proc),
(mu_proc - LSL) / (3 * sigma_proc))
print(f'Cp = {Cp:.2f}')
print(f'Cpk = {Cpk:.2f}')
# -----------------------------
# Plot
# -----------------------------
plt.figure(figsize=(15,8))
# Curva padrão (alvo)
plt.plot(x, y_ref, color='tab:blue', lw=2, label='Padrão: μ=0, σ=1')
# Curva processo real
plt.plot(x, y_proc, color='tab:orange', lw=2, label='Processo real: μ=0, σ=2')
# Limites de especificação
plt.axvline(LSL, color='black', linestyle='--', lw=1)
plt.axvline(USL, color='black', linestyle='--', lw=1)
plt.text(LSL, 0.005, 'LSL', ha='right', va='bottom')
plt.text(USL, 0.005, 'USL', ha='left', va='bottom')
# Linha da média alvo
plt.axvline(mu_proc, color='gray', linestyle=':', lw=1)
plt.text(mu_proc, max(y_ref)*1.02, 'μ (alvo)', ha='center', fontsize=9)
# -----------------------------
# Área fora da especificação (processo real)
# -----------------------------
# x < LSL
plt.fill_between(
x, 0, y_proc,
where=(x < LSL),
color='red', alpha=0.35,
label='Fora da especificação (abaixo do LSL)'
)
# x > USL
plt.fill_between(
x, 0, y_proc,
where=(x > USL),
color='red', alpha=0.35,
label='Fora da especificação (acima do USL)'
)
# -----------------------------
# Configurações
# -----------------------------
plt.title('Capabilidade do Processo: LSL, USL, Cp e Cpk', fontsize=15, pad=15)
plt.xlabel('Medida do Produto')
plt.ylabel('Densidade de Probabilidade')
plt.grid(alpha=0.3)
plt.legend(loc='upper right')
plt.ylim(bottom=0)
# Texto explicativo
texto = (
f"Cp = {Cp:.2f} | Cpk = {Cpk:.2f}\n"
"Interpretação:\n"
"- Cp compara a largura da distribuição com a largura da especificação.\n"
" Cp < 1 → o processo é mais largo que os limites: alta variabilidade.\n"
"- Cpk considera também o deslocamento da média.\n"
" Aqui μ está centrado, mas σ é grande → peças em excesso fora de LSL/USL.\n"
"- As áreas em vermelho mostram o risco direto: sucata, retrabalho, devolução."
)
plt.text(
0, -0.095,
texto,
ha='center', va='top', fontsize=9,
bbox=dict(facecolor='white', alpha=0.95, edgecolor='gray')
)
plt.tight_layout()
plt.show()
Cp = 0.50 Cpk = 0.50
⚙️ 3.1. Plot das Rolling Statistics (Estatísticas Móveis)
📌 Observe:
🚀 Primeiro, vamos checar se as estatísticas móveis são ou não constantes ao longo da série temporal.
type(dados)
pandas.core.frame.DataFrame
📌 Observe:
# Estatísticas móveis
window = 12
rolmean = dados['usuarios_ativos'].rolling(window=window).mean()
rolstd = dados['usuarios_ativos'].rolling(window=window).std()
# BLOCO 1 - GRÁFICO ESTÁTICO
fig1, ax1 = plt.subplots(figsize=(15, 6))
ax1.plot(dados['mes'], dados['usuarios_ativos'], color='blue', label='Original')
ax1.plot(dados['mes'], rolmean, color='red', label='Média Móvel (12)')
ax1.plot(dados['mes'], rolstd, color='black', label='Desvio Padrão (12)')
ax1.set_title('Estatísticas Móveis - Média e Desvio Padrão')
ax1.set_xlabel('Mês')
ax1.set_ylabel('Usuários Ativos')
ax1.legend(loc='best')
plt.show()
%matplotlib inline
from matplotlib.animation import FuncAnimation
from IPython.display import HTML
import numpy as np
x = dados['mes'].values
y = dados['usuarios_ativos'].values
# BLOCO 2 - ANIMAÇÃO
fig2, ax2 = plt.subplots(figsize=(15, 6))
ax2.set_title('Estatísticas Móveis - Média e Desvio Padrão (Animação)')
ax2.set_xlabel('Mês')
ax2.set_ylabel('Usuários Ativos')
y_max = np.nanmax([y, rolmean, rolstd])
ax2.set_xlim(x[0], x[-1])
ax2.set_ylim(0, y_max * 1.15)
line_orig, = ax2.plot([], [], color='blue', label='Original')
line_mean, = ax2.plot([], [], color='red', label='Média Móvel (12)')
line_std, = ax2.plot([], [], color='black', label='Desvio Padrão (12)')
ax2.legend(loc='best')
def init():
line_orig.set_data([], [])
line_mean.set_data([], [])
line_std.set_data([], [])
return line_orig, line_mean, line_std
def update(frame):
x_data = x[:frame]
line_orig.set_data(x_data, y[:frame])
line_mean.set_data(x_data, rolmean[:frame])
line_std.set_data(x_data, rolstd[:frame])
return line_orig, line_mean, line_std
anim = FuncAnimation(
fig2,
update,
init_func=init,
frames=len(dados),
interval=120,
blit=True
)
html_anim = HTML(anim.to_jshtml())
plt.close(fig2) # evita mostrar a figura estática da animação
html_anim # última linha da célula -> só aparece o player